// Copyright 2017 Baidu Inc. All Rights Reserved.
// Author: Su Hao (suhao@baidu.com)
//
// Description: The adapter for Memory Management.

#include "baidu_ca_internal.h"
#include "duer_log.h"

typedef struct _baidu_ca_memory_s {
    bca_context     context;
    bca_malloc_f    f_malloc;
    bca_realloc_f   f_realloc;
    bca_free_f      f_free;
#ifdef BCA_MEMORY_USAGE
    bca_size_t      alloc_counts;
    bca_size_t      alloc_size;
    bca_size_t      max_size;
#endif/*BCA_MEMORY_USAGE*/
} bca_memory_t;

BCA_LOC_IMPL bca_memory_t s_bca_memory = {NULL};

#ifdef BCA_MEMORY_USAGE

#define BCA_MEM_HDR_MASK        (0xFEDCBA98)

#define BCA_MEMORY_HEADER   \
    bca_u32_t   mask; \
    bca_size_t  size;

#define BCA_MEM_CONVERT(ptr)  (bca_memdbg_t *)(ptr ? ((char *)ptr - sizeof(bca_memhdr_t)) : NULL)

typedef struct _baidu_ca_memory_header_s {
    BCA_MEMORY_HEADER
} bca_memhdr_t;

typedef struct _baidu_ca_memory_debug_s {
    BCA_MEMORY_HEADER
    char        data[];
} bca_memdbg_t;

BCA_LOC_IMPL void* bca_memdbg_acquire(bca_memdbg_t* ptr, bca_size_t size) {
    if (ptr) {
        ptr->mask = BCA_MEM_HDR_MASK;
        ptr->size = size - sizeof(bca_memhdr_t);
        s_bca_memory.alloc_counts++;
        s_bca_memory.alloc_size += ptr->size;

        if (s_bca_memory.max_size < s_bca_memory.alloc_size) {
            s_bca_memory.max_size = s_bca_memory.alloc_size;
        }
    }

    return ptr ? ptr->data : NULL;
}

BCA_LOC_IMPL void bca_memdbg_release(bca_memdbg_t* p) {
    if (p) {
        if (p->mask != BCA_MEM_HDR_MASK) {
            DUER_LOGE("The memory <%x> has been trampled!!!", p->data);
        }

        s_bca_memory.alloc_counts--;
        s_bca_memory.alloc_size -= p->size;
    }
}

BCA_INT_IMPL void bca_memdbg_usage() {
    DUER_LOGI("bca_memdbg_usage: alloc_counts = %d, alloc_size = %d, max_size = %d",
             s_bca_memory.alloc_counts, s_bca_memory.alloc_size, s_bca_memory.max_size);
}

#endif/*BCA_MEMORY_USAGE*/

BCA_EXT_IMPL void baidu_ca_memory_init(bca_context context,
                                       bca_malloc_f f_malloc,
                                       bca_realloc_f f_realloc,
                                       bca_free_f f_free) {
    s_bca_memory.context = context;
    s_bca_memory.f_malloc = f_malloc;
    s_bca_memory.f_realloc = f_realloc;
    s_bca_memory.f_free = f_free;
}

BCA_INT_IMPL void* bca_malloc(bca_size_t size) {
    void* rs = NULL;

    if (s_bca_memory.f_malloc) {
#ifdef BCA_MEMORY_USAGE
        size += sizeof(bca_memhdr_t);
#endif
        rs = s_bca_memory.f_malloc(s_bca_memory.context, size);
#ifdef BCA_MEMORY_USAGE
        rs = bca_memdbg_acquire(rs, size);
#endif
    }

    return rs;
}

BCA_INT_IMPL void* bca_realloc(void* ptr, bca_size_t size) {
    void* rs = NULL;

    if (s_bca_memory.f_realloc || (s_bca_memory.f_malloc && s_bca_memory.f_free)) {
#ifdef BCA_MEMORY_USAGE
        ptr = BCA_MEM_CONVERT(ptr);
        size += sizeof(bca_memhdr_t);
        bca_memdbg_release(ptr);
#endif

        if (s_bca_memory.f_realloc) {
            rs = s_bca_memory.f_realloc(s_bca_memory.context, ptr, size);
        } else if (s_bca_memory.f_malloc && s_bca_memory.f_free) {
            rs = bca_malloc(size);

            if (rs) {
                BCA_MEMCPY(rs, ptr, size);
            }

            bca_free(ptr);
        } else {
            // do nothing
        }

#ifdef BCA_MEMORY_USAGE
        rs = bca_memdbg_acquire(rs, size);
#endif
    }

    return rs;
}

BCA_INT_IMPL void bca_free(void* ptr) {
    if (s_bca_memory.f_free) {
#ifdef BCA_MEMORY_USAGE
        ptr = BCA_MEM_CONVERT(ptr);
        bca_memdbg_release(ptr);
#endif
        s_bca_memory.f_free(s_bca_memory.context, ptr);
    }
}

BCA_INT_IMPL void* bca_malloc_ext(bca_size_t size, const char* file,
                                  bca_u32_t line) {
    void* rs = bca_malloc(size);
    DUER_LOGI(file, line, "bca_malloc_ext: addr = %x, size = %d", rs, size);

    if (rs) {
        BCA_MEMSET(rs, 0, size);
    }

    return rs;
}

BCA_INT_IMPL void* bca_realloc_ext(void* ptr, bca_size_t size, const char* file,
                                   bca_u32_t line) {
    void* rs = bca_realloc(ptr, size);
    DUER_LOGI(file, line,
                 "bca_realloc_ext: new_addr = %x, old_addr = %x, size = %d", rs, ptr, size);
    return rs;
}

BCA_INT_IMPL void bca_free_ext(void* ptr, const char* file, bca_u32_t line) {
    DUER_LOGI(file, line, "bca_free_ext: addr = %x", ptr);
    bca_free(ptr);
}
